浏览器:渲染流程

当请求结果的content-type=text/html时,浏览器就会将资源交给渲染引擎进行渲染,大概流程如下:

XI.png

一、解析HTML

浏览器接收到的HTML是字节流的形式,首先会对其进行词法分析,将其转换为一个个的token,分为Tag Token文本 Token,而,Tag token又分为StartTagEndTag

HTML 解析器维护了一个Token 栈结构,该 Token 栈主要用来计算节点之间的父子关系,在第一个阶段中生成的 Token 会被按照顺序压到这个栈中。

举个例子:

1
2
3
4
5
6
<html>
<boby>
<div>1</div>
<div>test</div>
</boby>
</html>

解析为:

img

二、构建DOM树

在解析的时候,就开始构建DOM树,步骤如下:

  • 如果压入到栈中的是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
  • 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
  • 如果分词器解析出来的是EndTag标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。

通过分词器产生的新 Token 就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。

img

三、构建CSS规则树

  1. 把CSS转换为浏览器能够理解的结构

    浏览器同样不能识别CSS格式的文档,需要将其转换为如下一样的格式:

    可以控制台中输入document.styleSheets就可以看到当前网页的所有样式了

  2. 转换样式表中的属性值,使其标准化

    CSS文本中有很多属性值,如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

    其中像em、rem、%等大小单位,都会通过计算转换为px,而颜色会转换为对应的rgb

  3. 计算出DOM树中每个节点的具体样式

    在计算每个节点的具体样式的时候,需要注意样式的继承层叠规则

    • 继承

      CSS中有很多样式时可以继承的比如font-size,举个例子:

      我们给boby设置一个font-size:20px的样式,那么它的所有子元素都会具有这么一个样式

    • 层叠

      层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法

四、生成布局树

布局树只包括可见的节点,会忽略不可见的节点,比如设置了display:none;样式的节点

并且计算出每个节点具体的坐标位置,以便后面绘制时使用

五、分层

一些复杂的3D变换、页面滚动,或者使用z-indexing做z轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)

拥有层叠上下文属性提升为单独的一个图层:明确定位属性的元素、定义透明属性的元素、使用CSS滤镜的元素等,都拥有层叠上下文属性。

需要剪裁(clip)的地方也会被创建为图层:把div的大小限定为200 * 200像素,而div里面的文字内容比较多,文字所显示的区域肯定会超出200 * 200的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在div区域,出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。

六、图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。

你也可以打开“开发者工具”的“Layers”标签,选择“document”层,来实际体验下绘制列表

img

七、栅格化(raster)操作

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程

所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的

通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。

八、合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

总结流程:

  • 渲染进程将HTML内容转换为能够读懂的DOM树结构。
  • 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。
  • 创建布局树,并计算元素的布局信息。
  • 对布局树进行分层,并生成分层树。
  • 为每个图层生成绘制列表,并将其提交到合成线程。
  • 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  • 合成线程发送绘制图块命令DrawQuad给浏览器进程。
  • 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上

重绘和重排的区别?

答:重排需要走完整个渲染流程,重绘不需要走布局流程(生成布局树、分层)